//*****************************************************
// Aitrox Information Technology
// http://www.proxima-ai.com
// Copyright 2021 Aitrox. All rights reserved.
//
// Filename: export.go
// CreateDate: 2023-05-06
// Author: zjp
// Description: 具体导出数据业务
//******************************************************

package service

import (
	"fmt"
	"os"
	"os/exec"
	"runtime"
	"strconv"
	"sync"
	"time"
	"usplatformserver/global"
	"usplatformserver/internal/model"
	"usplatformserver/internal/model/export"
	"usplatformserver/pkg/errcode"
	"usplatformserver/pkg/utils"

	"github.com/tealeg/xlsx"
)

// form中的内容表示该参数对应的key值，binding中required表示必填参数
type ExportRequest struct {
	Checkpoint *int   `form:"checkpoint" binding:"required"`
	PatientIds string `form:"patientIds" binding:"required"`
}

type DeleteFileRequest struct {
	FileName string `form:"fileName" binding:"required"`
}

// 文件拷贝通道信息
type chanCopyFile struct {
	SrcFilePath string
	DstFilePath string
}

// 患者信息文件夹名称
const FILE_NAME string = "patientInfo"

// 最大导出协程个数
const MAX_EXPORT_ROUTINES int = 2

var CheckpointTypeString = map[int]string{
	model.CheckpointThyroid: "thyroid",
	model.CheckpointBreast:  "breast",
	model.CheckpointHeart:   "cardiac",
}

type usageExportData struct {
	CheckId            string
	CheckStartDateTime string
	CheckEndDateTime   string
	CheckSpendTime     int
	IsUseReportPush    bool
	IsUseScan          bool
	IsUseAnalysis      bool
}

var TiradsValueString = map[int]string{
	-2: "1",
	-1: "2",
	0:  "3",
	1:  "4A",
	2:  "4B",
	3:  "4C",
	4:  "4C",
	5:  "5",
}

var ACRTiradsValueString = map[int]string{
	0: "1",
	1: "1",
	2: "2",
	3: "3",
	4: "4",
	5: "4",
	6: "4",
	7: "5",
}

var thyroidSheetHeaders = []string{
	"患者姓名", "患者时间戳", "患者ID", "检查号", "性别", "年龄", "图片名", "类型", "左右径", "前后径", "面积", "位置", "病理良恶性", "病理良恶性概率",
	"点状回声", "粗钙化", "周边钙化", "彗星尾", "回声", "实性", "边缘", "方位", "x1", "y1", "x2", "y2", "C-TIRADS", "ACR-TIRADS", "风险等级(C)", "风险等级(ACR)",
	"是否删除",
}

var breastSheetHeaders = []string{
	"患者姓名", "患者时间戳", "患者ID", "检查号", "性别", "年龄", "图片名", "类型", "位置", "方位", "边缘", "回声", "形状", "钙化", "后方回声", "左右径", "前后径",
	"面积", "x1", "y1", "x2", "y2", "病理良恶性", "病理良恶性概率", "BIRADS", "风险等级", "毛刺", "模糊", "微小分叶", "成角", "高回声晕", "是否删除",
}

var heartSheetHeaders = []string{
	"患者姓名", "患者时间戳", "患者ID", "检查号", "性别", "年龄", "图片名", "切面类型", "切面识别概率", "LVEF", "长径",
	"面积", "容积", "期相",
}

var usageSheetHeaders = []string{
	"检查号", "检查开始时间", "检查完成时间", "检查时间(s)", "是否使用报告推送", "是否使用结节分析", "是否使用标准扫查", "使用记录",
}

var carotidSheetHeaders = []string{
	"患者姓名", "患者时间戳", "患者ID", "检查号", "性别", "年龄", "图片名", "类型", "斑块厚度", "管腔内径", "狭窄率", "斑块长度", "回声", "纤维帽", "边缘", "钙化", "溃疡",
	"x1", "y1", "x2", "y2", "是否删除",
}

/**
 * @brief 获取导出数据
 * @param param-入参
 */
func (svc *Service) GetExportData(param *ExportRequest) (string, *errcode.Error) {
	var patientStamps = map[string]string{}
	var fileName = ""
	if *param.Checkpoint == model.CheckpointThyroid {
		var tNidusList []*export.ThyroidNidusInfo
		//获取导出数据
		tNidusList, err := svc.dao.GetThyroidExportData(param.PatientIds)
		if nil != err {
			return "", errcode.ErrorExportSelectFail
		}
		if len(tNidusList) == 0 {
			return "", errcode.ErrorExportDataEmpty
		}
		//获取AI导出数据
		tNidusAiList, err := svc.dao.GetThyroidExportDataAi(param.PatientIds)
		if nil != err {
			return "", errcode.ErrorExportSelectFail
		}
		if len(tNidusAiList) == 0 {
			return "", errcode.ErrorExportDataEmpty
		}
		//导出数据转EXCEL
		patientStamps, fileName, err = exportThyroid2Excel(tNidusList, tNidusAiList)
		if err != nil {
			global.Logger.Infof("exportThyroid2Excel failed: %v", err)
			return "", errcode.ErrorExportFail
		}
	} else if *param.Checkpoint == model.CheckpointBreast {
		var tNidusList []*export.BreastNidusInfo
		//获取导出数据
		tNidusList, err := svc.dao.GetBreastExportData(param.PatientIds)
		if nil != err {
			return "", errcode.ErrorExportSelectFail
		}
		if len(tNidusList) == 0 {
			return "", errcode.ErrorExportDataEmpty
		}
		//获取AI到处数据
		tNidusAiList, err := svc.dao.GetBreastExportDataAi(param.PatientIds)
		if nil != err {
			return "", errcode.ErrorExportSelectFail
		}
		if len(tNidusAiList) == 0 {
			return "", errcode.ErrorExportDataEmpty
		}
		//导出数据转EXCEL
		patientStamps, fileName, err = exportBreast2Excel(tNidusList, tNidusAiList)
		if err != nil {
			global.Logger.Infof("exportBreast2Excel failed: %v", err)
			return "", errcode.ErrorExportFail
		}
	} else if *param.Checkpoint == model.CheckpointHeart {
		var tMeasureList []*export.HeartMeasureInfo
		//获取导出数据
		tMeasureList, err := svc.dao.GetHeartExportData(param.PatientIds)
		if nil != err {
			return "", errcode.ErrorExportSelectFail
		}
		if len(tMeasureList) == 0 {
			fmt.Println(len(tMeasureList))
			return "", errcode.ErrorExportDataEmpty
		}
		//导出数据转EXCEL
		patientStamps, fileName, err = exportHeart2Excel(tMeasureList)
		if err != nil {
			global.Logger.Infof("exportHeart2Excel failed: %v", err)
			return "", errcode.ErrorExportFail
		}
	}

	//导出使用率表格
	usageData, err := svc.dao.GetUsageExportData(param.PatientIds)
	if err != nil {
		global.Logger.Infof("GetUsageExportData failed: %v", err)
	}
	usageFileName, err := exportUsageData2Excel(usageData)
	if err != nil {
		global.Logger.Infof("exportUsageData2Excel failed: %v", err)
	}

	//复制文件夹
	patientInfoFileName, err := copyPatientInfoFile(patientStamps, *param.Checkpoint)
	if nil != err {
		global.Logger.Errorf("copy patient info file failed: %v", err)
		return "", errcode.ErrorExportFail
	}
	//压缩文件
	tarFile, err := compressFile(patientInfoFileName, fileName, usageFileName)
	if nil != err {
		global.Logger.Errorf("compress file failed: %v", err)
		return "", errcode.ErrorExportFail
	}

	return tarFile, nil
}

/**
 * @brief 获取导出数据2(取消patientInfo文件复制)
 * @param param-入参
 */
func (svc *Service) GetExportData2(param *ExportRequest) (string, *errcode.Error) {
	var patientStamps = map[string]string{}
	var fileName = ""
	if *param.Checkpoint == model.CheckpointThyroid {
		var tNidusList []*export.ThyroidNidusInfo
		//获取导出数据
		tNidusList, err := svc.dao.GetThyroidExportData(param.PatientIds)
		if nil != err {
			return "", errcode.ErrorExportSelectFail
		}
		if len(tNidusList) == 0 {
			return "", errcode.ErrorExportDataEmpty
		}
		//获取AI导出数据
		tNidusAiList, err := svc.dao.GetThyroidExportDataAi(param.PatientIds)
		if nil != err {
			return "", errcode.ErrorExportSelectFail
		}
		if len(tNidusAiList) == 0 {
			return "", errcode.ErrorExportDataEmpty
		}
		//导出数据转EXCEL
		patientStamps, fileName, err = exportThyroid2Excel(tNidusList, tNidusAiList)
		if err != nil {
			global.Logger.Infof("exportThyroid2Excel failed: %v", err)
			return "", errcode.ErrorExportFail
		}
	} else if *param.Checkpoint == model.CheckpointBreast {
		var tNidusList []*export.BreastNidusInfo
		//获取导出数据
		tNidusList, err := svc.dao.GetBreastExportData(param.PatientIds)
		if nil != err {
			return "", errcode.ErrorExportSelectFail
		}
		if len(tNidusList) == 0 {
			return "", errcode.ErrorExportDataEmpty
		}
		//获取AI到处数据
		tNidusAiList, err := svc.dao.GetBreastExportDataAi(param.PatientIds)
		if nil != err {
			return "", errcode.ErrorExportSelectFail
		}
		if len(tNidusAiList) == 0 {
			return "", errcode.ErrorExportDataEmpty
		}
		//导出数据转EXCEL
		patientStamps, fileName, err = exportBreast2Excel(tNidusList, tNidusAiList)
		if err != nil {
			global.Logger.Infof("exportBreast2Excel failed: %v", err)
			return "", errcode.ErrorExportFail
		}
	} else if *param.Checkpoint == model.CheckpointHeart {
		var tMeasureList []*export.HeartMeasureInfo
		//获取导出数据
		tMeasureList, err := svc.dao.GetHeartExportData(param.PatientIds)
		if nil != err {
			return "", errcode.ErrorExportSelectFail
		}
		if len(tMeasureList) == 0 {
			fmt.Println(len(tMeasureList))
			return "", errcode.ErrorExportDataEmpty
		}
		//导出数据转EXCEL
		patientStamps, fileName, err = exportHeart2Excel(tMeasureList)
		if err != nil {
			global.Logger.Infof("exportHeart2Excel failed: %v", err)
			return "", errcode.ErrorExportFail
		}
	}

	global.Logger.Infof("export patients: %v", patientStamps)

	//导出使用率表格
	usageData, err := svc.dao.GetUsageExportData(param.PatientIds)
	if err != nil {
		global.Logger.Infof("GetUsageExportData failed: %v", err)
	}
	usageFileName, err := exportUsageData2Excel(usageData)
	if err != nil {
		global.Logger.Infof("exportUsageData2Excel failed: %v", err)
	}

	//压缩文件
	tarFile, err := compressFile2(fileName, usageFileName)
	if nil != err {
		global.Logger.Errorf("compress file failed: %v", err)
		return "", errcode.ErrorExportFail
	}

	return tarFile, nil
}

func (svc *Service) DeleteFile(fileName string) error {
	strCmd := fmt.Sprintf("rm -rf %v", fileName)
	cmd := exec.Command("bash", "-c", strCmd)
	err := cmd.Start()
	if err != nil {
		global.Logger.Infof("remove export file failed: %v", err)
	}
	return err
}

// 导出甲状腺病灶数据至excel表格
func exportThyroid2Excel(ls, lsi []*export.ThyroidNidusInfo) (map[string]string, string, error) {
	var patientStamps = map[string]string{}
	var fileName string = fmt.Sprintf("thyroid_%v.xlsx", time.Now().UnixMilli())

	//创建excel文件
	file := xlsx.NewFile()
	//添加表
	sheet, err := file.AddSheet("甲状腺结节数据")
	if nil != err {
		return patientStamps, "", err
	}
	//添加标题行
	row := sheet.AddRow()
	//添加列标
	for _, v := range thyroidSheetHeaders {
		cell := row.AddCell()
		cell.Value = v
	}

	var strTmp string = ""
	//添加数据
	for i, v := range ls {
		strTmp = v.NidusExtInfo.TimeStamp
		if _, ok := patientStamps[strTmp]; !ok {
			patientStamps[strTmp] = strTmp
		}
		appendThyroidExcelRow(sheet, v, getCheckStatusString(v.CheckStatus))
		if i < len(lsi) {
			//修改数据后跟一条AI原始数据
			appendThyroidExcelRow(sheet, lsi[i], "AI")
		}
	}
	//保存文件
	file.Save(fileName)

	return patientStamps, fileName, nil
}

func appendThyroidExcelRow(sheet *xlsx.Sheet, v *export.ThyroidNidusInfo, checkStatus string) {
	row := sheet.AddRow()
	//患者姓名
	appendCell(row, v.NidusExtInfo.PatientName)
	//缓存患者时间戳，以便后续拷贝文件夹
	appendCell(row, v.NidusExtInfo.TimeStamp)
	appendCell(row, v.NidusExtInfo.PatientId)
	appendCell(row, v.NidusExtInfo.CheckId)
	appendCell(row, getGenderString(v.NidusExtInfo.Sex))
	strTmp := ""
	if v.NidusExtInfo.Age > 0 {
		strTmp = strconv.Itoa(int(v.NidusExtInfo.Age))
	}
	appendCell(row, strTmp)
	appendCell(row, v.NidusExtInfo.ImageName)
	appendCell(row, checkStatus)
	appendCell(row, getFloat32String(v.LRDiameter))
	appendCell(row, getFloat32String(v.FBDiameter))
	appendCell(row, getFloat32String(v.Area))
	appendCell(row, getLobeString(v.Lobe))
	appendCell(row, getMalignantString(v.Malignant))
	appendCell(row, getFloat32String(v.MalignantProb))
	appendCell(row, getEchogenicFociString(v.EchogenicFoci))
	appendCell(row, getYNString(v.MacroCalcifications))
	appendCell(row, getYNString(v.PeripheralCalcifications))
	appendCell(row, getYNString(v.Comet))
	appendCell(row, getEchogenicString(v.Echogenic))
	appendCell(row, getSolidString(v.Solid))
	appendCell(row, getMarginString(v.Circumscribed))
	appendCell(row, getTallerthanwideString(v.Tallerthanwide))
	appendCell(row, strconv.Itoa(v.X1))
	appendCell(row, strconv.Itoa(v.Y1))
	appendCell(row, strconv.Itoa(v.X2))
	appendCell(row, strconv.Itoa(v.Y2))
	ctirads := getCTiradsValue(v)
	appendCell(row, TiradsValueString[ctirads])
	acrtirads := getACRTiradsValue(v)
	appendCell(row, GetACRTiradsValueText(acrtirads))
	cl := getTiradsLevel(ctirads)
	appendCell(row, cl)
	acrl := getACRTiradsLevel(acrtirads)
	appendCell(row, acrl)
	appendCell(row, getYesNoString(v.ImageIsDelete == 1))
}

// 导出乳腺病灶数据至excel表格
func exportBreast2Excel(ls, lsi []*export.BreastNidusInfo) (map[string]string, string, error) {
	var patientStamps = map[string]string{}
	var fileName string = fmt.Sprintf("breast_%v.xlsx", time.Now().UnixMilli())

	//创建excel文件
	file := xlsx.NewFile()
	//添加表
	sheet, err := file.AddSheet("乳腺结节数据")
	if nil != err {
		return patientStamps, "", err
	}
	//添加标题行
	row := sheet.AddRow()
	//添加列标
	for _, v := range breastSheetHeaders {
		cell := row.AddCell()
		cell.Value = v
	}

	var strTmp string = ""
	//添加数据
	for i, v := range ls {
		//缓存患者时间戳，以便后续拷贝文件夹
		strTmp = v.NidusExtInfo.TimeStamp
		if _, ok := patientStamps[strTmp]; !ok {
			patientStamps[strTmp] = strTmp
		}
		appendBreastExcelRow(sheet, v, getCheckStatusString(v.CheckStatus))
		if i < len(lsi) {
			appendBreastExcelRow(sheet, lsi[i], "AI")
		}
	}
	//保存文件
	file.Save(fileName)

	return patientStamps, fileName, nil
}

func appendBreastExcelRow(sheet *xlsx.Sheet, v *export.BreastNidusInfo, checkStatus string) {
	row := sheet.AddRow()
	//患者姓名
	appendCell(row, v.NidusExtInfo.PatientName)
	appendCell(row, v.NidusExtInfo.TimeStamp)
	appendCell(row, v.NidusExtInfo.PatientId)
	appendCell(row, v.NidusExtInfo.CheckId)
	appendCell(row, getGenderString(v.NidusExtInfo.Sex))
	strTmp := ""
	if v.NidusExtInfo.Age > 0 {
		strTmp = strconv.Itoa(int(v.NidusExtInfo.Age))
	}
	appendCell(row, strTmp)
	appendCell(row, v.NidusExtInfo.ImageName)
	appendCell(row, checkStatus)
	appendCell(row, getBreastPosString(v.Pos))
	appendCell(row, getOrientationString(v.Orientation))
	appendCell(row, getBreastMarginString(v.Margin))
	appendCell(row, getBreastEchogenicString(v.EchoGenicity))
	appendCell(row, getBreastShapeString(v.Shape))
	appendCell(row, getBreastEchogenicFociString(v.MicroCalcifications))
	appendCell(row, getAcousticString(v.Acoustic))
	appendCell(row, getFloat32String(v.LRDiameter))
	appendCell(row, getFloat32String(v.FBDiameter))
	appendCell(row, getFloat32String(v.Area))
	appendCell(row, strconv.Itoa(v.X1))
	appendCell(row, strconv.Itoa(v.Y1))
	appendCell(row, strconv.Itoa(v.X2))
	appendCell(row, strconv.Itoa(v.Y2))
	appendCell(row, getMalignantString(v.Malignant))
	appendCell(row, getFloat32String(v.MalignantProb))
	btirads := getBTiradsValue(v)
	appendCell(row, TiradsValueString[btirads])
	bl := getTiradsLevel(btirads)
	appendCell(row, bl)
	appendCell(row, getYNString(v.Glitch))
	appendCell(row, getYNString(v.Vague))
	appendCell(row, getYNString(v.Microlobulation))
	appendCell(row, getYNString(v.Angled))
	appendCell(row, getYNString(v.Hyperecho))
	appendCell(row, getYesNoString(v.ImageIsDelete == 1))
}

func appendCarotidExcelRow(sheet *xlsx.Sheet, v *export.CarotidNidusInfo, checkStatus string) {
	row := sheet.AddRow()
	//患者姓名
	appendCell(row, v.NidusExtInfo.PatientName)
	appendCell(row, v.NidusExtInfo.TimeStamp)
	appendCell(row, v.NidusExtInfo.PatientId)
	appendCell(row, v.NidusExtInfo.CheckId)
	appendCell(row, getGenderString(v.NidusExtInfo.Sex))
	strTmp := ""
	if v.NidusExtInfo.Age > 0 {
		strTmp = strconv.Itoa(int(v.NidusExtInfo.Age))
	}
	appendCell(row, strTmp)
	appendCell(row, v.NidusExtInfo.ImageName)
	appendCell(row, checkStatus)
	appendCell(row, getFloat32String(v.VerticalDiameter))
	appendCell(row, getFloat32String(v.BloodDiameter))
	appendCell(row, getFloat32String(v.StenosisRate))
	appendCell(row, getFloat32String(v.HorizontalDiameter))
	appendCell(row, getCarotidEchoString(v.Echogenicity, v.EchogenicityGrade))
	appendCell(row, getCarotidFiberString(v.FibrousCap))
	appendCell(row, getCarotidEdgeString(v.Edge))
	appendCell(row, getCarotidEdgeString(v.Calcification))
	appendCell(row, getCarotidUclcerString(v.Uclcer))
	appendCell(row, strconv.Itoa(v.X1))
	appendCell(row, strconv.Itoa(v.Y1))
	appendCell(row, strconv.Itoa(v.X2))
	appendCell(row, strconv.Itoa(v.Y2))
	appendCell(row, getYesNoString(v.ImageIsDelete == 1))
}

// 导出心脏数据至excel表格
func exportHeart2Excel(ls []*export.HeartMeasureInfo) (map[string]string, string, error) {
	var patientStamps = map[string]string{}
	var fileName string = fmt.Sprintf("heart_%v.xlsx", time.Now().UnixMilli())

	//创建excel文件
	file := xlsx.NewFile()
	//添加表
	sheet, err := file.AddSheet("心脏左室测量数据")
	if nil != err {
		return patientStamps, "", err
	}
	//添加标题行
	row := sheet.AddRow()
	//添加列标
	for _, v := range heartSheetHeaders {
		cell := row.AddCell()
		cell.Value = v
	}

	var strTmp string = ""
	//添加数据
	for _, v := range ls {
		row = sheet.AddRow()
		//患者姓名
		appendCell(row, v.NidusExtInfo.PatientName)
		//缓存患者时间戳，以便后续拷贝文件夹
		strTmp = v.NidusExtInfo.TimeStamp
		if _, ok := patientStamps[strTmp]; !ok {
			patientStamps[strTmp] = strTmp
		}
		appendCell(row, strTmp)
		appendCell(row, v.NidusExtInfo.PatientId)
		appendCell(row, v.NidusExtInfo.CheckId)
		appendCell(row, getGenderString(v.NidusExtInfo.Sex))
		strTmp = ""
		if v.NidusExtInfo.Age > 0 {
			strTmp = strconv.Itoa(int(v.NidusExtInfo.Age))
		}
		appendCell(row, strTmp)
		appendCell(row, v.NidusExtInfo.ImageName)
		appendCell(row, getChamberTypeString(v.ChamberType))
		appendCell(row, getFloat32String(v.ChamberProb))
		appendCell(row, getFloat32String(v.LVEF))
		appendCell(row, getFloat32String(v.LongDiameter))
		appendCell(row, getFloat32String(v.Area))
		appendCell(row, getFloat32String(v.Volume))
		appendCell(row, getPeriodTypeString(v.Period))
	}
	//保存文件
	file.Save(fileName)

	return patientStamps, fileName, nil
}

// 导出使用率数据至excel表格
func exportUsageData2Excel(ls []*export.ClientUsageStatistics) (string, error) {
	if len(ls) <= 0 {
		return "", nil
	}
	var fileName string = fmt.Sprintf("usage_%v.xlsx", time.Now().UnixMilli())
	//创建excel文件
	file := xlsx.NewFile()
	//添加表
	sheet, err := file.AddSheet("使用率数据")
	if nil != err {
		fmt.Println(err)
		return "", err
	}
	//添加标题行
	row := sheet.AddRow()
	//添加列标
	for _, v := range usageSheetHeaders {
		cell := row.AddCell()
		cell.Value = v
	}
	//拼接使用数据
	var listData []usageExportData
	var data usageExportData
	for i, v := range ls {
		if v.EventId == 3 {
			data.IsUseScan = true
		}
		if v.EventId == 6 {
			data.IsUseAnalysis = true
		}
		if v.EventId == 22 {
			data.IsUseReportPush = true
		}
		if v.EventId == 2 {
			if data.CheckStartDateTime != "" && v.CheckDateTime != data.CheckStartDateTime {
				data.CheckEndDateTime = v.CheckDateTime
				data.CheckSpendTime = int(GetCheckCostTime(data.CheckStartDateTime, data.CheckEndDateTime))
				listData = append(listData, data) //一次检查结束
				data.IsUseScan = false
				data.IsUseAnalysis = false
				data.IsUseReportPush = false
			}
			data.CheckStartDateTime = v.CheckDateTime
			data.CheckId = v.CheckId
		}
		if i == (len(ls) - 1) {
			//最后一条数据无法统计结束时间，默认为空
			data.CheckEndDateTime = ""
			data.CheckSpendTime = 0
			listData = append(listData, data)
		}
	}
	fmt.Println("listData len: ", len(listData))
	//添加excel
	var isUseClient bool = false
	for _, v := range listData {
		row = sheet.AddRow()
		//检查ID
		appendCell(row, v.CheckId)
		//检查开始时间
		appendCell(row, FormatDatetime(v.CheckStartDateTime))
		//检查结束时间
		appendCell(row, FormatDatetime(v.CheckEndDateTime))
		//检查时间
		fmt.Println("cost time: ", v.CheckSpendTime)
		appendCell(row, fmt.Sprintf("%v", v.CheckSpendTime))
		//是否使用报告推送
		appendCell(row, getYesNoString(v.IsUseReportPush))
		//是否使用结节分析
		appendCell(row, getYesNoString(v.IsUseAnalysis))
		//是否使用标准扫查
		appendCell(row, getYesNoString(v.IsUseScan))
		//是否使用客户端
		isUseClient = false
		if v.IsUseAnalysis || v.IsUseScan || v.IsUseReportPush {
			isUseClient = true
		}
		appendCell(row, getYesNoString(isUseClient))
	}
	//保存文件
	err = file.Save(fileName)
	if err != nil {
		return "", err
	}
	return fileName, nil
}

func GetCheckCostTime(startTime, endTime string) int {
	layout := "2006-01-02T15:04:05+08:00"
	t1, err := time.Parse(layout, startTime)
	global.Logger.Infof("time parse: %v", err)
	t2, err := time.Parse(layout, endTime)
	global.Logger.Infof("time parse: %v", err)
	duration := t2.Sub(t1)

	return int(duration.Seconds())
}

func GetCheckCostTimeByLayout(startTime, endTime, layout string) int {
	t1, err := time.Parse(layout, startTime)
	global.Logger.Infof("time parse: %v", err)
	t2, err := time.Parse(layout, endTime)
	global.Logger.Infof("time parse: %v", err)
	duration := t2.Sub(t1)

	return int(duration.Seconds())
}

func FormatDatetime(dt string) string {
	if dt == "" {
		return ""
	}
	layout := "2006-01-02T15:04:05+08:00"
	t1, _ := time.Parse(layout, dt)
	layout2 := "2006-01-02 15:04:05"
	return t1.Format(layout2)
}

// 复制指定文件至当前文件夹(单独协程)
func copyPatientInfoFile(patientStamps map[string]string, checkpoint int) (string, error) {
	var patientInfoFileName string = fmt.Sprintf("%v_%v", FILE_NAME, time.Now().UnixMilli())
	if runtime.GOOS == "linux" {
		appDir, _ := os.Getwd()
		pathDst := fmt.Sprintf("%v/%v", appDir, patientInfoFileName)
		if isExists, _ := utils.PathExists(pathDst); !isExists {
			err := os.Mkdir(pathDst, 0777)
			if nil != err {
				global.Logger.Infof("创建%s文件夹失败", pathDst)
				return "", err
			}
		}

		//协程通道集
		var mapChanCopyFileSet = map[int]chan chanCopyFile{}
		//创建协程个数，起始为任务总数的一半
		routineCount := len(patientStamps)/2 + len(patientStamps)%2
		//如果比最大协程数大，取最大协程数
		if routineCount > MAX_EXPORT_ROUTINES {
			routineCount = MAX_EXPORT_ROUTINES
		}

		fmt.Println("export routines: ", routineCount)

		//通道任务数
		var chanTaskCount = len(patientStamps)/routineCount + len(patientStamps)%2
		for i := 0; i < routineCount; i++ {
			if _, ok := mapChanCopyFileSet[i]; !ok {
				mapChanCopyFileSet[i] = make(chan chanCopyFile, chanTaskCount)
			}
		}

		fmt.Println("mapChanSet len: ", len(mapChanCopyFileSet))

		var dstPatientStampDir string
		var pIndex int = 0
		var mapChanSetIndex int = 0
		var dirName string
		for _, v := range patientStamps {

			dirName = v

			//患者文件夹判断，文件名命名规则修改为时间戳_xxx，需要判断是否存在当前时间戳开头的文件夹，然后返回真实文件名
			srcPatientPath := fmt.Sprintf("%s/%s/%s", global.AppSettings.UsclientPath, FILE_NAME, dirName)
			if isExists, _, pathSrc, dirTmp := utils.PathContains(srcPatientPath); isExists {
				srcPatientPath = pathSrc
				dirName = dirTmp
			}
			fmt.Println("srcPatientPath: ", srcPatientPath)

			//创建目标文件夹路径
			dstPatientStampDir = fmt.Sprintf("%s/%s", pathDst, dirName)
			if isExists, _ := utils.PathExists(dstPatientStampDir); !isExists {
				err := os.Mkdir(dstPatientStampDir, 0777)
				if nil != err {
					global.Logger.Infof("创建%s文件夹失败", dirName)
					return "", err
				}
			}
			fmt.Println("dstPatientPath: ", dstPatientStampDir)

			//拷贝文件
			srcFilePath := fmt.Sprintf("%s/%s", srcPatientPath, CheckpointTypeString[checkpoint])
			var cpChan chanCopyFile = chanCopyFile{
				DstFilePath: dstPatientStampDir,
				SrcFilePath: srcFilePath,
			}

			//计算通道索引
			mapChanSetIndex = pIndex / chanTaskCount
			if mapChanSetIndex < len(mapChanCopyFileSet) {
				mapChanCopyFileSet[mapChanSetIndex] <- cpChan
			}

			pIndex++
		}

		var wg sync.WaitGroup
		//执行通道任务
		for _, v := range mapChanCopyFileSet {
			close(v)
			wg.Add(1)
			go runCopyFileFolder(v, &wg)
		}

		//等待所有协程执行完成
		wg.Wait()
	}
	return patientInfoFileName, nil
}

// 执行复制文件夹操作
func runCopyFileFolder(cpChan chan chanCopyFile, wg *sync.WaitGroup) {
	for v := range cpChan {
		fmt.Println(v)
		strCmd := fmt.Sprintf("cp -r %s %s", v.SrcFilePath, v.DstFilePath)
		cmd := exec.Command("bash", "-c", strCmd)
		cmd.Dir = v.DstFilePath
		//err := utils.Copy(v.SrcFilePath, v.DstFilePath)
		err := cmd.Run()
		if err != nil {
			global.Logger.Info(v)
			global.Logger.Infof("拷贝文件失败: %v", err)
		}
	}
	//该协程工作已完成
	fmt.Println("copy done")
	defer wg.Done()
}

// 压缩文件
func compressFile(patientInfoFileName, fileName, usageFileName string) (string, error) {
	var strCmd string
	var zipFileName string
	if runtime.GOOS == "linux" {
		path := "./" + patientInfoFileName
		//判断patientInfo文件夹是否存在
		if isExists, err := utils.PathExists(path); !isExists {
			return "", err
		}
		//判断excel文件是否存在
		if isExists, err := utils.PathExists(fmt.Sprintf("./%s", fileName)); !isExists {
			return "", err
		}
		//判断使用率excel文件是否存在
		if isExists, err := utils.PathExists(fmt.Sprintf("./%s", usageFileName)); !isExists {
			return "", err
		}

		//压缩文件名
		zipFileName = fmt.Sprintf("exportdata_%d.zip", time.Now().UnixMilli())
		//压缩
		strCmd = fmt.Sprintf("zip -r %s %s %s %s", zipFileName, patientInfoFileName, fileName, usageFileName)
		cmd := exec.Command("bash", "-c", strCmd)
		err := cmd.Run()
		if err != nil {
			return "", err
		}

		//删除文件
		strCmd = fmt.Sprintf("rm -rf %s %s %s", patientInfoFileName, fileName, usageFileName)
		cmd = exec.Command("bash", "-c", strCmd)
		cmd.Dir = "./"
		cmd.Run()
	}

	return zipFileName, nil
}

// 压缩文件
func compressFile2(fileName, usageFileName string) (string, error) {
	var strCmd string
	var zipFileName string
	if runtime.GOOS == "linux" {
		//判断excel文件是否存在
		if isExists, err := utils.PathExists(fmt.Sprintf("./%s", fileName)); !isExists {
			return "", err
		}
		//判断使用率excel文件是否存在
		if isExists, err := utils.PathExists(fmt.Sprintf("./%s", usageFileName)); !isExists {
			return "", err
		}

		//压缩文件名
		zipFileName = fmt.Sprintf("exportdata_%d.zip", time.Now().UnixMilli())
		//压缩
		strCmd = fmt.Sprintf("zip -r %s %s %s", zipFileName, fileName, usageFileName)
		cmd := exec.Command("bash", "-c", strCmd)
		err := cmd.Run()
		if err != nil {
			return "", err
		}

		//删除文件
		strCmd = fmt.Sprintf("rm -rf %s %s", fileName, usageFileName)
		cmd = exec.Command("bash", "-c", strCmd)
		cmd.Dir = "./"
		cmd.Run()
	}

	return zipFileName, nil
}

func appendCell(row *xlsx.Row, v string) {
	cell := row.AddCell()
	cell.Value = v
}

func getGenderString(sex uint8) string {
	if sex == 0 {
		return "男"
	} else if sex == 1 {
		return "女"
	}
	return "男"
}

func getFloat32String(f float32) string {
	return strconv.FormatFloat(float64(f), 'f', 2, 32)
}

func getLobeString(v int) string {
	switch v {
	case 1:
		return "右叶"
	case 2:
		return "左叶"
	case 3:
		return "峡部"
	default:
		return "未知"
	}
}

func getMalignantString(v int) string {
	switch v {
	case 0:
		return "良性"
	case 1:
		return "恶性"
	default:
		return strconv.Itoa(int(v))
	}
}

func getEchogenicFociString(v int) string {
	switch v {
	case 0:
		return "无微钙化"
	case 1:
		return "可疑胶质"
	case 2:
		return "意义不明确"
	case 3:
		return "微钙化"
	default:
		return strconv.Itoa(int(v))
	}
}

func getYNString(v int) string {
	if v == 0 {
		return "无"
	} else {
		return "有"
	}
}

func getYesNoString(v bool) string {
	if v {
		return "是"
	} else {
		return "否"
	}
}

func getEchogenicString(v int) string {
	switch v {
	case 0:
		return "高回声"
	case 1:
		return "等回声"
	case 2:
		return "低回声"
	case 3:
		return "无回声"
	case 4:
		return "极低回声"
	case 5:
		return "因钙化掩盖无法评估"
	default:
		return strconv.Itoa(int(v))
	}
}

func getSolidString(v int) string {
	switch v {
	case export.ETubercleStructCystic:
		return "囊性"
	case export.ETubercleStructMixedCompose:
		return "囊实混合"
	case export.ETubercleStructSolid:
		return "实性"
	case export.ETubercleStructCalcficationMasking:
		return "因钙化掩盖无法评估"
	default:
		return strconv.Itoa(int(v))
	}
}

func getMarginString(v int) string {
	switch v {
	case 0:
		return "光整"
	case 1:
		return "不规则"
	case 2:
		return "模糊"
	case 3:
		return "甲状腺外侵犯"
	default:
		return strconv.Itoa(int(v))
	}
}

func getTallerthanwideString(v int) string {
	switch v {
	case 0:
		return "水平位"
	case 1:
		return "垂直位"
	default:
		return strconv.Itoa(int(v))
	}
}

func getBreastPosString(v int) string {
	switch v {
	case 0:
		return "左侧"
	case 1:
		return "右侧"
	default:
		return strconv.Itoa(int(v))
	}
}

func getBreastEchogenicString(v int) string {
	switch v {
	case 0:
		return "高回声"
	case 1:
		return "等回声"
	case 2:
		return "低回声"
	case 3:
		return "无回声"
	case 4:
		return "不均匀回声"
	case 5:
		return "囊实性混合回声"
	default:
		return strconv.Itoa(int(v))
	}
}

func getOrientationString(v int) string {
	switch v {
	case 0:
		return "平行"
	case 1:
		return "不平行"
	default:
		return strconv.Itoa(int(v))
	}
}

func getBreastMarginString(v int) string {
	switch v {
	case 0:
		return "光整"
	case 1:
		return "不光整"
	case 2:
		return "模糊"
	case 3:
		return "成角"
	case 4:
		return "微小分叶"
	case 5:
		return "毛刺状"
	default:
		return strconv.Itoa(int(v))
	}
}

func getBreastShapeString(v int) string {
	switch v {
	case 0:
		return "圆形"
	case 1:
		return "椭圆形"
	case 2:
		return "不规则"
	default:
		return strconv.Itoa(int(v))
	}
}

func getBreastEchogenicFociString(v int) string {
	switch v {
	case 0:
		return "无微钙化"
	case 1:
		return "有微钙化"
	default:
		return strconv.Itoa(int(v))
	}
}

func getAcousticString(v int) string {
	switch v {
	case 0:
		return "无改变"
	case 1:
		return "混合"
	case 2:
		return "衰减"
	case 3:
		return "增强"
	default:
		return strconv.Itoa(int(v))
	}
}

func getChamberTypeString(v int) string {
	switch v {
	case 2:
		return "二腔"
	case 3:
		return "三腔"
	case 4:
		return "四腔"
	case 5:
		return "其它"
	default:
		return strconv.Itoa(int(v))
	}
}

func getPeriodTypeString(v int) string {
	switch v {
	case 1:
		return "舒张末期"
	case 2:
		return "收缩末期"
	default:
		return strconv.Itoa(int(v))
	}
}

func getCTiradsValue(nidusInfo *export.ThyroidNidusInfo) int {
	if nidusInfo == nil {
		return 0
	}
	var value int = 0
	if nidusInfo.Solid == export.ETubercleStructCystic &&
		nidusInfo.Echogenic == export.ETubercleEchoNone {
		return -1
	}
	//实性 +1
	if nidusInfo.Solid == export.ETubercleStructSolid {
		value += 1
	}

	//极低回声 +1
	if nidusInfo.Echogenic == export.ETubercleEchoLower {
		value += 1
	}

	//微钙化 +1
	if nidusInfo.EchogenicFoci == export.ETubercleCalcificTiny {
		value += 1
	}

	//边缘非光整 +1
	if nidusInfo.Circumscribed != export.ETubercleEdgeFitting {
		value += 1
	}

	//结节各项指标为良性，忽略垂直位加分
	if value != 0 {
		if nidusInfo.Tallerthanwide == export.ETubercleDirectVertical {
			value += 1
		}
	}
	//彗星尾伪像 -1
	if 1 == nidusInfo.Comet {
		value -= 1
	}

	return value
}

func getACRTiradsValue(nidusInfo *export.ThyroidNidusInfo) int {
	if nidusInfo == nil {
		return 0
	}
	var value int = 0
	if nidusInfo.Solid == export.ETubercleStructCystic &&
		nidusInfo.Echogenic == export.ETubercleEchoNone {
		return 1
	}
	//实性 +2 囊实混合 +1
	if nidusInfo.Solid == export.ETubercleStructSolid {
		value += 2
	} else if nidusInfo.Solid == export.ETubercleStructMixedCompose {
		value += 1
	}
	//极低回声 +3 低回声 +2 等回声或高回声 +1
	if nidusInfo.Echogenic == export.ETubercleEchoLower {
		value += 3
	} else if nidusInfo.Echogenic == export.ETubercleEchoLow {
		value += 2
	} else if nidusInfo.Echogenic == export.ETubercleEchoEqual ||
		nidusInfo.Echogenic == export.ETubercleEchoHigh {
		value += 1
	}
	//微钙化 +3 周边钙化 +2 粗钙化 +1
	if nidusInfo.EchogenicFoci == export.ETubercleCalcificTiny {
		value += 3
	} else if 1 == nidusInfo.PeripheralCalcifications {
		value += 2
	} else if 1 == nidusInfo.MacroCalcifications {
		value += 1
	}
	//不规则 +2 甲状腺外侵犯 +3
	if nidusInfo.Circumscribed == export.ETubercleEdgeIrregular {
		value += 2
	} else if nidusInfo.Circumscribed == export.ETubercleEdgeExternalAggression {
		value += 3
	}
	//结节各项指标为良性，忽略垂直位加分
	if value != 0 {
		//垂直位 +3
		if nidusInfo.Tallerthanwide == export.ETubercleDirectVertical {
			value += 3
		}
	}

	return value
}

func GetACRTiradsValueText(value int) string {
	if value < 0 {
		return "1"
	}
	if value > 7 {
		return "5"
	}
	return ACRTiradsValueString[value]
}

func getBTiradsValue(nidusInfo *export.BreastNidusInfo) int {
	if nidusInfo == nil {
		return 0
	}
	var value int = 0
	//不规则 +1
	if nidusInfo.Shape == export.ETubercleShapeIrregular {
		value += 1
	}
	//有钙化 +1
	if nidusInfo.MicroCalcifications == export.EBreastTubercleCalcifics {
		value += 1
	}
	//除了边缘光整其它都+1
	if nidusInfo.Margin != export.EBreastTubercleEdgeFitting {
		value += 1
	}

	//垂直位 +1
	if nidusInfo.Orientation == export.ETubercleDirectVertical {
		value += 1
	}

	return value
}

func getCheckStatusString(v uint32) string {
	switch v {
	case 0:
		return "未复核"
	case 1:
		return "已复核"
	default:
		return "未复核"
	}
}

func getCarotidEchoString(v, level int) string {
	var str string
	switch v {
	case 0:
		str = "非均质"
		break
	case 1:
		str = "均质"
		break
	default:
		str = "未知"
		break
	}
	switch level {
	case 0:
		str += "低回声"
		break
	case 1:
		str += "等回声"
		break
	case 2:
		str += "高回声"
		break
	default:
		str += "未知"
		break
	}
	return str
}

func getCarotidFiberString(v int) string {
	switch v {
	case 0:
		return "破裂"
	case 1:
		return "完整"
	default:
		return "未知"
	}
}

func getCarotidEdgeString(v int) string {
	switch v {
	case 0:
		return "不规则"
	case 1:
		return "规则"
	default:
		return "未知"
	}
}

func getCarotidCalcString(v int) string {
	switch v {
	case 0:
		return "不可见"
	case 1:
		return "可见"
	default:
		return "未知"
	}
}

func getCarotidUclcerString(v int) string {
	switch v {
	case 0:
		return "不可见"
	case 1:
		return "可见"
	default:
		return "未知"
	}
}
